package com.yesallchina.wxapp.officialaccount.api;

import com.yesallchina.wxapp.officialaccount.api.beans.ParamName;
import com.yesallchina.wxapp.officialaccount.api.beans.param.*;
import com.yesallchina.wxapp.officialaccount.exception.WxResponseException;
import com.yesallchina.wxapp.officialaccount.ext.BusinessExtendHandler;
import com.yesallchina.wxapp.officialaccount.service.WxOfficialAccountInitializer;
import com.yesallchina.wxapp.officialaccount.utils.*;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * 微信支付
 */
public class PayApi extends WebController {

    private final static Logger log = LoggerFactory.getLogger(PayApi.class);


    private final static String TRADE_TYPE_NATIVE = "Native";
    private final static String TRADE_TYPE_JSAPI = "JSAPI";

    private static Validator validator = null;

    static {
        validator = Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(false)
                .buildValidatorFactory()
                .getValidator();
    }


    private WxOfficialAccountInitializer wxInit;

    private BasicSupportApi basicSupportApi;

    public PayApi(BasicSupportApi basicSupportApi) {
        this.basicSupportApi = basicSupportApi;
        this.wxInit = basicSupportApi.getWxInit();
    }

    /**
     * 获取预支付签名
     *
     * @return
     */
    public TreeMap<String, String> getPrePaySign(PayParam param) {
        param.setAppId(wxInit.getCommonProp().getAppid());
        param.setMchId(wxInit.getCommonProp().getPayMchId());
        param.setNonceStr(WxUtils.getDigitRandom(10));
        param.setSignType("MD5");
        // 支付采用统一的回调，然后在回调接口中实现扩展处理
        param.setNotifyUrl(wxInit.getCommonProp().getPayNotify());

        if (!StringUtils.hasText(param.getTradeType())) {
            // 默认公众号内支付
            param.setTradeType(TRADE_TYPE_JSAPI);
        }
        if (!StringUtils.hasText(param.getFeeType())) {
            // 默认人民币
            param.setFeeType("CNY");
        }
        if (TRADE_TYPE_NATIVE.equalsIgnoreCase(param.getTradeType())
                && !StringUtils.hasText(param.getProductId())) {
            throw new WxResponseException("当支付方式是Native支付时，缺少productId字段数据");
        }
        if (TRADE_TYPE_JSAPI.equalsIgnoreCase(param.getTradeType())
                && !StringUtils.hasText(param.getOpenid())) {
            throw new WxResponseException("当支付方式是JSAPI支付时，缺少openid字段数据");
        }
        validPayParam(param);

        TreeMap<String, String> params = new TreeMap<>();
        params.putAll(XmlUtils.xmlToMap(XmlUtils.beanToXml(param)));

        TreeMap<String, String> target = new TreeMap<>();
        setPaySign(params, target);
        String str = XmlUtils.mapToXml(target);
        if (log.isDebugEnabled()) {
            log.debug("[微信支付]: 生成预支付信息,参数 \r\n : {}", str);
        }
        // 主要是获取prepayId
        String url = wxInit.getApiProp().getPayUnifiedorder();
        str = WxHttpUtils.postXML(url, str, wxInit.getCommonProp());
        if (log.isDebugEnabled()) {
            log.info("[微信支付]: 生成预支付信息,结果 \r\n {}", str);
        }
        PayPreSignDTO mPayPreSign = XmlUtils.xmlToBean(str, PayPreSignDTO.class);
        if (mPayPreSign == null || !StringUtils.hasText(mPayPreSign.getPrepayId())) {
            throw new WxResponseException("支付签名失败");
        }
        mPayPreSign.setTradeId(param.getTradeId());
        return setClientPaySign(mPayPreSign);
    }

    private <T> void validPayParam(T param) {
        Set<ConstraintViolation<T>> violationSet = validator.validate(param);
        boolean hasError = violationSet != null && violationSet.size() > 0;
        if (hasError) {
            for (ConstraintViolation<T> violation : violationSet) {
                throw new WxResponseException(violation.getMessage());
            }
        }
    }

    private void setPaySign(TreeMap<String, String> src, TreeMap<String, String> target) {
        String sign = getSign(src, target);
        target.put(ParamName.Pay.SIGN, sign);
    }

    private String getSign(TreeMap<String, String> src, TreeMap<String, String> target) {
        String sign = "";
        Iterator<String> keys = src.keySet().iterator();
        while (keys.hasNext()) {
            String key = keys.next();
            if (!StringUtils.hasText(src.get(key))) {
                continue;
            }
            sign += (key + "=" + src.get(key) + "&");
            if (null != target) {
                target.put(key, src.get(key));
            }
        }
        sign += "key=" + wxInit.getCommonProp().getPayMchKey();
        return Md5Encrypt.md5(sign).toUpperCase();
    }

    private TreeMap<String, String> setClientPaySign(PayPreSignDTO payPreSign) {
        TreeMap<String, String> src = new TreeMap<>();
        TreeMap<String, String> target = new TreeMap<>();
        // 腾讯不要脸啊，参数名都不一致
        src.put("appId", payPreSign.getAppId());
        src.put("timeStamp", System.currentTimeMillis() + "");
        src.put("nonceStr", payPreSign.getNonceStr());
        src.put("package", "prepay_id=" + payPreSign.getPrepayId());
        src.put("signType", "MD5");
        setPaySign(src, target);
        target.put("tradeId", payPreSign.getTradeId());
        if (log.isDebugEnabled()) {
            log.info("[微信支付]: 生成客户端支付调用签名信息 \r\n {} ", JacksonUtils.generate(target));
        }
        return target;
    }


    @RequestMapping(value = "/wxofficialaccount/paynotify")
    @ResponseBody
    public String payNotify(HttpServletRequest request) {
        try {
            StringBuffer receivedData = new StringBuffer();
            InputStreamReader inReader = new InputStreamReader(request.getInputStream(), "UTF-8");
            BufferedReader mReader = new BufferedReader(inReader);
            String mLine = null;
            while ((mLine = mReader.readLine()) != null) {
                receivedData.append(mLine.trim());
            }
            if (!StringUtils.hasText(receivedData.toString())) {
                return "faild";
            }
            if (!payNotify(receivedData.toString())){
                return "faild";
            }
        } catch (IOException e) {
            return "faild";
        }
        return "<xml>" +
                "<return_code><![CDATA[SUCCESS]]></return_code>" +
                "<return_msg><![CDATA[OK]]></return_msg>" +
                "</xml>";
    }

    private boolean payNotify(String notifyXml) {
        log.info("[微信支付] : 支付结果通知， [{}]", notifyXml);
        Map<String,String> mNotify = XmlUtils.xmlToMap(notifyXml);
        if (!"SUCCESS".equals(String.valueOf(mNotify.get("return_code")).toUpperCase())) {
            return false;
        }
        if (!checkSign(notifyXml)) {
            log.error("[微信支付]：支付结果通知，签名验证失败");
            return false;
        }
        PayResultNotifyDTO result = XmlUtils.xmlToBean(notifyXml, PayResultNotifyDTO.class);
        if (result == null) {
            log.error("[微信支付]：支付结果通知，未能解析出通知结果");
            return false;
        }
        // 签名检查通过回调
        String signCallbackClass = wxInit.getSpiProp().getPayCallbackHandlerName();
        if (StringUtils.hasText(signCallbackClass)) {
            BusinessExtendHandler handler = wxInit.getAppCtx().getBean(signCallbackClass);
            if (handler != null) {
                handler.handle(JacksonUtils.generate(result));
            }
        }
        return true;
    }

    private boolean checkSign(String notifyXml) {
        TreeMap<String, String> param = new TreeMap() {{
            putAll(XmlUtils.xmlToMap(notifyXml));
        }};
        String sign = param.remove("sign");
        String chkSign = getSign(param, null);
        return sign.equals(chkSign);
    }


    /**
     * 发起退款
     *
     */
    public boolean refund(RefundParam param) {
        param.setAppId(wxInit.getCommonProp().getAppid());
        param.setMchId(wxInit.getCommonProp().getPayMchId());
        param.setNonceStr(WxUtils.getDigitRandom(10));
        param.setSignType("MD5");
        param.setNotifyUrl(wxInit.getCommonProp().getRefundNotity());
        validPayParam(param);
        Map<String, String> mTarget = XmlUtils.xmlToMap(XmlUtils.beanToXml(param));
        TreeMap<String, String> mSrc = new TreeMap<>();
        mSrc.putAll(mTarget);
        mSrc.remove("sign");
        TreeMap<String, String> target = new TreeMap<>();
        target.put("sign", getSign(mSrc, target));
        String str = XmlUtils.mapToXml(target);
        log.info("[微信支付申请退款]， 参数 = [{}]", str);
        String response = WxHttpUtils.postXML(wxInit.getApiProp().getRefund(), str, wxInit.getCommonProp());
        if (!refundNotify(response)){
            return false;
        }
        return true;
    }


    /**
     * 退款结果回调
     *
     * @return
     */
    @RequestMapping(value = "/wxofficialaccount/refundnotify")
    @ResponseBody
    public String payRefundNotify(HttpServletRequest request) {
        try {
            StringBuffer receivedData = new StringBuffer();
            InputStreamReader inReader = new InputStreamReader(request.getInputStream(), "UTF-8");
            BufferedReader mReader = new BufferedReader(inReader);
            String mLine = null;
            while ((mLine = mReader.readLine()) != null) {
                receivedData.append(mLine.trim());
            }
            if (!StringUtils.hasText(receivedData.toString())) {
                return "faild";
            }
            if (!refundNotify(receivedData.toString())) {
                return "faild";
            }
        } catch (IOException e) {
            return "faild";
        }
        return "<xml>" +
                "<return_code><![CDATA[SUCCESS]]></return_code>" +
                "<return_msg><![CDATA[OK]]></return_msg>" +
                "</xml>";
    }

    private boolean refundNotify(String notifyXml) {
        log.info("[微信退款]：退款结果通知， [{}]", notifyXml);
        Map<String,String> mNotify = XmlUtils.xmlToMap(notifyXml);
        if (!"SUCCESS".equals(String.valueOf(mNotify.get("return_code")).toUpperCase())) {
            return false;
        }
        if (!checkSign(notifyXml)) {
            log.error("[微信退款]：退款通知结果签名验证失败");
            return false;
        }
        RefundNotifyDTO result = XmlUtils.xmlToBean(notifyXml, RefundNotifyDTO.class);
        if (result == null) {
            log.error("[微信退款]：退款通知通知，未能解析出通知结果");
            return false;
        }
        String signCallbackClass = wxInit.getSpiProp().getRefundCallbackHandlerName();
        if (StringUtils.hasText(signCallbackClass)) {
            BusinessExtendHandler handler = wxInit.getAppCtx().getBean(signCallbackClass);
            if (handler != null) {
                handler.handle(JacksonUtils.generate(result));
            }
        }
        return true;
    }


}
